#include"load_balance.h"
#include"main_server.h"

// 构造函数
load_balance::load_balance(int modid, int cmdid){
    _modid = modid;
    _cmdid = cmdid;
    _access_cnt = 0;
    _status = PULLING;
    _last_update_time = time(NULL);
}

// 向远程的DNS服务中发送ID_GetRouteRequest请求
int load_balance::pull(){
    // 封装一个请求包
    lars::GetRouteRequest request;
    request.set_modid(_modid);
    request.set_cmdid(_cmdid);

    // 将请求包发给dns_queue队列中
    dns_queue->send(request);

    // 设置当前状态为正在拉取中
    _status = PULLING; 

    return 0;
}


// 将dns返回的数据中的host信息更新到到当前的load_balance中
int load_balance::update(lars::GetRouteResponse &response){
    printf("load_balance::update\n");
    // 1.确保当前dns service返回的response中有host数据
    assert(response.host_size() != 0);

    // 创建两个集合，用于删除不应该存在的host信息
    // remote_hosts是应该存在的host, need_delete是应该删除的host
    std::set<uint64_t> remote_hosts;
    std::set<uint64_t> need_delete;
    
    // 2.插入新的hot信息到_host_map中
    for(int i=0; i< response.host_size(); i++){
        // 2.1取出host信息
        const lars::HostInfo host = response.host(i);

        // 得到主机的ip+port键值对
        uint64_t key = (((uint64_t)host.ip()) << 32 ) + host.port();

        // 将dns给的host信息添加到remote_hosts中
        remote_hosts.insert(key);

        // 判断当前的key能否在mao中找到
        if(_host_map.find(key) == _host_map.end()){
            // 如果没找到，说明当前不存在键值对，应该添加一个新的
            host_info* info = new host_info(host.ip(), host.port(), config.lb_init_succ_cnt);
            if(info == NULL){
                fprintf(stderr, "load_balance::update create host_info error\n");
                return 1;
            }

            _host_map[key] = info;

            // 将新增的host信息，增加到空闲列表信息中
            _idle_list.push_back(info);
        }
    }

    // 3.得到需要减少的host信息, 遍历整个_host_map表，检查其中不应该存在的数据
    for(host_map_it it=_host_map.begin(); it!=_host_map.end(); it++){
        
        if(remote_hosts.find(it->first) == remote_hosts.end()){
            // 表示当前的host未在dns service中, 表示当前主机已经被修改或删除
            need_delete.insert(it->first);
        }
    }

    // 4.删除主机信息, 遍历need_delete集合
    for(std::set<uint64_t>::iterator it=need_delete.begin(); it!=need_delete.end(); it++){
        // 取出当前host
        host_info* info = _host_map[*it];

        // 判断是否过载
        if(info->overload == true){
            // 从过载列表中删除
            _overload_list.remove(info);
        }
        else{
            // 从空闲队列中删除
            _idle_list.remove(info);
        }

        delete info;
    }

    // 更新load_balance中最后一次的更新时间参数
    _last_update_time = time(NULL);
    
    // 重置状态为NEW
    _status = NEW;

    return 0;
}


// 获取一个可用的host信息
int load_balance::choice_one_host(lars::GetHostResponse &response){
    int ret = lars::RET_SUCC;
    // 1.判断idle_list是否为空
    if(_idle_list.empty()){
        // 如果为空，则无空闲节点

        // 判断是否超过阈值probe_num
        if(_access_cnt >= config.lb_probe_num){
            // 如果超过，则从overload_list中获取节点
            _access_cnt = 0;
            get_host_from_list(response, _overload_list);
        }
        else{
            // 如果没有超过阈值，说明目前确实没有可用节点，此时全部节点都是overload状态
            ret = lars::RET_OVERLOAD;
        }
    }
    else{
        // 如果非空，则存在空闲节点，并且overload列表为空，
        if(_overload_list.empty()){
            // 则选择一个idel节点返回 
            get_host_from_list(response, _idle_list);
        }
        else{
            // 给overload列表一个机会，判断是否超过阈值probe_num
            if(_access_cnt >= config.lb_probe_num){
                // 如果超过，则从overload_list中获取节点
                _access_cnt = 0;
                get_host_from_list(response, _overload_list);  
            }
            else{
                // 如果没有超过阈值，则选择一个idel节点返回
                get_host_from_list(response, _idle_list);
            }
        }
    }
    _access_cnt++;
    return ret;
}


// 从idel_list或者overload_list中得到一个host节点
void load_balance::get_host_from_list(lars::GetHostResponse &response, host_list &list){
    // 从list中选择第一个结点
    host_info* host = list.front();

    // 尝试将结点中的数据赋值给消息
    //HostInfo自定义类型,proto3并没提供set方法,而是通过mutable_接口返回HostInfo的指针,可根据此指针进行赋值操作
    lars::HostInfo *l_host = response.mutable_host();
    l_host->set_ip(host->ip);
    l_host->set_port(host->port);


    // 将第一个结点放置最后一个位置
    list.pop_front();
    list.push_back(host);
}

// 根据api上报的信息调整内部节点
void load_balance::report(int ip, int port, int retcode){
    // 请求一个时间，用来判断是否超时
    long current_time = time(NULL);
    
    // 0.得到key
    uint64_t key = (((uint64_t)ip) << 32) + port;

    // 1.得到真实的host IP
    struct in_addr inadrr;
    inadrr.s_addr = htonl(ip);
    std::string true_ip = inet_ntoa(inadrr);
 
    // 2.查找对应的host信息，取出host信息
    if(_host_map.find(key) != _host_map.end()){
        
        host_info *host = _host_map[key];
        
        // 2.1计数统计当前的成功率和失败率
        if(retcode == lars::RET_SUCC){
            host->vsucc++;
            host->rsucc++;

            host->contin_succ++;
            host->contin_err = 0;
        }
        else{
            host->verr++;
            host->rerr++;

            host->contin_succ = 0;
            host->contin_err++;
        }
        
        // 2.2 检查节点状态(检查idle结点是否满足overload条件，检查overload结点是否满足idle条件)
        if(host->overload == false && retcode != lars::RET_SUCC){
            // 当前状态为：不超载，但是不能正常使用
            bool overload = false;
            // 计算失败率
            double err_rate = host->verr * 1.0 / (host->vsucc + host->verr);
            
            // 检查失败率条件
            if(err_rate > config.lb_err_rate){
                overload = true;
            }

            // 检查连续失败次数条件
            if(overload == false && host->contin_err >= (uint32_t)config.lb_contin_err_limit){
                overload = true;
            }

            // 最终将idel结点修改为overload结点
            if(overload == true){
                printf("host ip:%s,port:%d set overload\n", true_ip.c_str(), port);
                
                // 切换状态为overload
                host->set_overload(config.lb_init_err_cnt);
                
                // 将host结点从idle中移出，移入overload中
                _idle_list.remove(host);
                _overload_list.push_back(host);
                return ;
            }
        }
        else if(host->overload == true && retcode == lars::RET_SUCC){
            // 当前状态为：超载，但是能正常使用
            bool idle = false;
            
            // 计算成功率
            double succ_rate = host->vsucc * 1.0 / (host->vsucc + host->verr);
            
            // 检查成功率条件
            if(succ_rate > config.lb_succ_rate){
                idle = true;
            }

            // 检查连续成功次数条件
            if(idle == false && host->contin_succ >= (uint32_t)config.lb_contin_succ_limit){
                idle = true;
            }

            // 最终将overload结点修改为idle结点
            if(idle == true){
                printf("host ip:%s,port:%d set idle\n", true_ip.c_str(), port);
                
                // 切换状态为idle
                host->set_idle(config.lb_init_succ_cnt);
                
                // 将host结点从overload中移出，移入idle中
                _overload_list.remove(host);
                _idle_list.push_back(host);
                return ;
            }
        }
        
        
        // 周期检查是否超时，需要重置结点
        if(host->overload == false ){
            // 是一个idle结点, 
            if((current_time-host->idle_ts) > config.lb_idle_timeout){
                // 时间到达超时时限，清空之前累计的数据
                host->set_idle(config.lb_init_succ_cnt);
            }    
        }
        else{
            // 是一个overload结点
            if((current_time-host->overload_ts)>config.lb_overload_timeout){
                // 时间到达超时时限，将overload结点转变为idle结点
                // 切换状态为idle
                host->set_idle(config.lb_init_succ_cnt);
                
                // 将host结点从overload中移出，移入idle中
                _overload_list.remove(host);
                _idle_list.push_back(host);
            }
        }
        

    }

}


// 将当前的load_balance中的host情况发送给report_queue，用于上报远程reporter service服务器
void load_balance::commit(){
    // 只有当前的map中非空，存在host信息，才会上报
    if(!this->empty()){
        // 1.封装上报消息
        lars::ReportStatusRequest request;
        request.set_modid(_modid);
        request.set_cmdid(_cmdid);
        request.set_ts(time(NULL));
        // 设置一个固定上报者
        request.set_caller(1234);

        // 2.从idle列表中取出所有的host信息上报
        for(host_list_it it= _idle_list.begin(); it!=_idle_list.end(); it++){
            host_info* host = *it;

            // 制作HostCallResult消息
            lars::HostCallResult callresult;
            callresult.set_ip(host->ip);
            callresult.set_port(host->port);
            callresult.set_succ(host->rsucc);
            callresult.set_err(host->rerr);
            callresult.set_overload(host->overload);

            request.add_results()->CopyFrom(callresult);
        }

        // 3.从overload列表中取出所有的host信息上报
        for(host_list_it it= _overload_list.begin(); it!=_overload_list.end(); it++){
            host_info* host = *it;

            // 制作HostCallResult消息
            lars::HostCallResult callresult;
            callresult.set_ip(host->ip);
            callresult.set_port(host->port);
            callresult.set_succ(host->rsucc);
            callresult.set_err(host->rerr);
            callresult.set_overload(host->overload);

            request.add_results()->CopyFrom(callresult);
        }

        // 4.将组装好的消息放入report_queue中，发给远程的report service
        report_queue->send(request);

    }
}



// 获取当前的所有host信息
void load_balance::get_all_hosts(lars::GetRouteResponse &response){
    
    // 遍历整个host_map获取所有的host信息
    for(host_map_it it=_host_map.begin(); it!=_host_map.end(); it++){
        // 得到host的IP和port
        int32_t ip = (uint32_t)(it->first>>32);
        int port = (uint32_t)(it->first);

        // 设置host信息
        lars::HostInfo host;
        host.set_ip(ip);
        host.set_port(port);

        // 将host信息整合到response中
        response.add_host()->CopyFrom(host);
    }
    
}




